"use strict";

import {Native} from "./native.js";

/**
 *
 * 蓝牙中心操作
 * - 支持浏览器:**否**
 * @static
 * @version 2
 * @author GuoHua Liu
 * @copyright Copyright © 2019 GuoHua Liu. All rights reserved.
 */

class central {

    /**
     * @private
     * @param {string} name
     * @param {function} onevent
     * @param {*} context
     */
    static addObserver(name, onevent, context) {
        Native.getInstance().addObserver("ble/" + name, onevent, context);
        return this;
    }

    /**
     * @private
     * @param {string} name
     * @param {string} onevent
     */
    static removeObserver(name, onevent) {
        Native.getInstance().delObserver("ble/" + name, onevent);
        return this;
    }

    /**
     * 添加观察者：观察手机蓝牙状态变化事件，比如用户对手机蓝牙进行了打开或者关闭操作，就会触发观察方法被调用
     * @param {function} onevent
     * - 事件结构 `{data:{state:"unknown"|"resetting"|"unsupported"|"unauthorized"|"poweredOff"|"poweredOn"}}`
     * - 可缺省:**否**
     * @param {*} context 指定onevent被调用时的this。
     * - 可缺省:**是**,在你不使用this的情况可以被缺省
     */
    static addStateObserver(onevent, context) {
        return central.addObserver("state", onevent, context);
    }

    /**
     * 移除观察者：观察手机蓝牙状态变化事件
     * @param {function} onevent
     */
    static removeStateObserver(onevent) {
        return central.removeObserver("state", onevent);
    }

    /**
     * 添加观察者：观察连上外设事件
     * @param {function} onevent 事件结构 {data:{uuid:"外设uuid", name:"设备名称", ...}}
     */
    static addConnectObserver(onevent, context) {
        return central.addObserver("connect", onevent, context);
    }

    /**
     * 删除观察者：观察连上外设事件
     * @param {function} onevent 观察方法
     * @param {*} context 调用onevent的上下文，当回调的时候onevent里面的this会指向context
     */
    static removeConnectObserver(onevent) {
        return central.removeObserver("connect", onevent);
    }

    /**
     * 添加观察者：观察发现外设事件
     * @param {function} onevent 事件结构`{data:{事件数据},...}`。
     * data实例
     * ```
        {
        "state" : "disconnected",
        "rssi" : -59,
        "name" : "HNK-F0",
        "advData" : {
            "manufacturer" : "0600fe0101acfd01000000",
            "services" : [
            "FEE7",
            "FEE8"
            ],
            "localName" : "HNK-F0",
            "isConnectable" : true
        },
        "uuid" : "ED6BF5FB-A627-674A-0EF1-4A6E1740D50D"
        }
     * ```
     * @param {*} context
     */
    static addDiscoverObserver(onevent, context) {
        return central.addObserver("discover", context, onevent);
    }

    /**
     * 移除观察者：观察发现外设事件
     * @param {function} onevent 观察方法
     */
    static removeDiscoverObserver(onevent) {
        return central.removeObserver("discover", onevent);
    }

    /**
     * 添加观察者：观察外设断连事件
     * @param {function} onevent 事件结构 {data:{uuid:"外设uuid", name:"设备名称", ...}}
     * @param {*} context
     */
    static addDisconnectObserver(onevent, context) {
        return central.addObserver("disconnect", context, onevent);
    }

    /**
     * 移除观察者：观察外设断连事件
     * @param {function} onevent
     */
    static removeDisconnectObserver(onevent) {
        return central.removeObserver("disconnect", onevent);
    }

    /**
     * 添加观察者：观察发起连接失败事件
     * @param {function} onevent 事件结构 {data:{uuid:"外设uuid", name:"设备名称", ...}}
     * @param {*} context
     */
    static addFailToConnectObserver(onevent, context) {
        return central.addObserver("failToConnect", context, onevent);
    }

    /**
     * 移除观察者：观察发起连接失败事件
     * @param {function} onevent
     */
    static removeFailToConnectObserver(onevent) {
        return central.removeObserver("failToConnect", onevent);
    }

    /**
     * 添加观察者：设备被从列表中删除
     * 当设备列表过长的时候，系统会尝试移除不在连接状态的一个或者多个设备
     * @deprecated 不再建议使用
     * @param {function} onevent 事件结构 {data:{uuids:[uuid]}}
     * @param {*} context
     */
    static addBeDeleteObserver(onevent, context) {
        return central.addObserver("remove", context, onevent);
    }

    /**
     * 移除观察者：设备被从列表中删除
     * @deprecated 不再建议使用
     * @param {function} onevent
     */
    static removeBeDeleteObserver(onevent) {
        return central.removeObserver("remove", onevent);
    }

    /**
     * 将不关心的外设（非自己app所要操作的、其他厂家的蓝牙设备）添加到忽略列表，被忽略的外设将不会再收到扫描通知，可以节省内存提高效率
     * @param {{uuid:string}} aPeripheral 外设
     */
    static ignore(aPeripheral) {
        if (!Native.getInstance().isHos()) {
            return;
        }
        Native.getInstance().callNative ("ble", {"method": "ignore", "uuid": aPeripheral.uuid}, false);
    }

    /**
     * 执行扫描。为节省电量，在不需要继续扫描的情况，比如扫描到用户所需的设备后，应该调用stopScan方法，停止扫描
     * @param {Array<string>} services 指定外设广播服务的uuid，可以避免扫描出其他厂商的蓝牙。不填将扫描所有设备；
     * @param {number} notificationInterval 限制两次回调间的时间（MS）
     * 如果想让app后台继续能发现设备，那么必须指定。
     */
    static scan(services, notificationInterval) {
        if (!Native.getInstance().isHos()) {
            return;
        }
        notificationInterval = notificationInterval ? notificationInterval : 999;
        Native.getInstance().callNative ("ble", {"method": "scan", "services": services, "notificationInterval": notificationInterval/1000}, false);
    }
    /**
     * 停止扫描，对应scan方法。
     */
    static stopScan() {
        if (!Native.getInstance().isHos()) {
            return;
        }
        Native.getInstance().callNative ("ble", {"method": "stopScan"}, false);
    }

    /**
     * 请求连接外设
     * @param {Object} peripheral 外设，结构{uuid:设备uuid}，该设备应该在peripherals列表中，
     * 如果不在，应该先扫描到该设备后再执行连接
     */
    static connect(peripheral) {
        if (!Native.getInstance().isHos()) {
            return;
        }
        Native.getInstance().callNative ("ble", {"method": "connect", "uuid": peripheral.uuid}, false);
    }

    /**
     * 请求断开外设连接
     * @param {Object} peripheral 外设，结构`{uuid:设备uuid}`
     * @param {Boolean} isClean 是否同时从外设列表中删除
     * - `false`:依然缓存外设
     * - `true` :从内存移除该外设
     * - 可缺省的: **是**，行为等同设置为`false`
     */
    static cancelConnect(peripheral, isClean) {
        if (!Native.getInstance().isHos()) {
            return;
        }
        Native.getInstance().callNative ("ble", {
            "method": "cancelConnect",
            "uuid": peripheral.uuid,
            "isClean": isClean
        }, false);
    }

    /**
     * 请求断开所有外设连接
     * @param {Boolean} isClean 是否清理缓存
     * - `false`:依然缓存外设信息
     * - `true` :从内存移除所有设备，清空外设列表
     * - 可缺省的: **是**，行为等同设置为`false`
     */
    static cancelAllConnect(isClean) {
        if (!Native.getInstance().isHos()) {
            return;
        }
        Native.getInstance().callNative ("ble", {"method": "cancelAllConnect","isClean": isClean}, false);
    }

    /**
     * 判断蓝牙是否打开,在调用scan等方法前应该判断蓝牙开关是否打开，
     * 如果没有打开，应该引导用户先打开开关，再进行其他操作
     * @returns {Boolean} true:打开 false:关闭
     */
    static isPoweredOn() {
        if (!Native.getInstance().isHos()) {
            return false;
        }
        let state = Native.getInstance().syncCallNative ("ble", {"method": "state"}).state;
        return state == "poweredOn";
    }

    /**
     * 获取底层已经扫描到的外设，可以用option选项指定某种状态的设备，缺省返回所有状态的外设。
     * 一般获取一次后就可以通过事件获知扫描到的设备数量和状态变化，无需频繁使用该函数查询。比如某个设备断开，就会从
     * 底层列表移除，下次连接需要重新扫描，蓝牙开关关闭，底层列表就会全部清空。
     * @param {Object} option 结构:{"state":"disconnected" | "connecting" | "connected" | "disconnecting"}
     * @returns {Array} 已经扫描到的外设列表 结构:[{uuid:"uudi",state:"状态",name:"名称",...},...]
     */
    static peripherals(option) {
        if (!Native.getInstance().isHos()) {
            return [];
        }
        let resp = Native.getInstance().syncCallNative ("ble", {"method": "peripherals", "option": option});
        return resp.peripherals;
    }

    /**
     * ASNC配对后的外设无法scan到，但是可以用该函数获取系统或者其他app已经连接上的外设，
     * 注意返回的设备信息中没有广播数据，可以在建立起和app的连接后，通过和设备通讯进一步确认是否为自家设备。
     * 如果已经有了scan到的外设信息，则可以以scan到的为准。
     * @param {Array<string>} 指定一个或者多个要查找外设包含有的服务uuid
     * @returns {Array} 已经扫描到的外设列表 结构:[{uuid:"uudi",state:"状态",name:"名称",...},...]
     */
    static retrieveConnected(services) {
        if (!Native.getInstance().isHos()) {
            return [];
        }
        if (Native.getInstance().isAndroid()) {
            return [];
        }
        let resp = Native.getInstance().syncCallNative ("ble", {"method": "retrieveConnected", "services": services});
        return resp.peripherals;
    }
}

/**
 * 蓝牙外设操作
 * @static
 */
class peripheral {

    /**
     * 添加外设观察者
     * @param {Object} aPeripheral 包含外设uuid的对象即可 结构{uuid:”设备的uuid“}
     * @param {string} name 事件名称
     * @param {function} onevent 观察函数 事件结构 {data:{数据内容根据具体的事件而定}}
     * @param {*} context 上下文，期待onevent被调用时this的指向
     * @returns {peripheral}
     */
    static addObserver(aPeripheral, name, onevent, context) {
        Native.getInstance().addObserver("ble/peripheral/" + name + "/" + aPeripheral.uuid, onevent, context);
        return this;
    }
    /**
     * 删除外设观察者
     * @param {*} aPeripheral
     * @param {*} name
     * @param {*} onevent
     * @returns {peripheral}
     */
    static removeObserver(aPeripheral, name, onevent) {
        Native.getInstance().delObserver("ble/peripheral/" + name + "/" + aPeripheral.uuid, onevent);
        return this;
    }

    /**
     * 添加观察者：发现服务
     * @param {Object} aPeripheral
     * @param {function} onevent 事件结构 {data:{"services":服务uuid数组}}
     * @param {*} context
     * @returns {peripheral}
     */
    static addDiscoverServicesObserver(aPeripheral, onevent, context) {
        console.log("ble", "addDiscoverServicesObserver", aPeripheral);
        peripheral.addObserver(aPeripheral, "discoverServices", onevent, context);
    }

    /**
     * 移除观察者：发现服务
     * @param {Object} aPeripheral
     * @param {function} onevent
     * @returns {peripheral}
     */
    static removeDiscoverServicesObserver(aPeripheral, onevent) {
        return peripheral.removeObserver(aPeripheral, "discoverServices", onevent);
    }

    /**
     * 添加观察者：发现特征
     * @param {Object} aPeripheral
     * @param {function} onevent 事件结构 {data:{service:"特征所属服务uuid", "characteristics":[特征uuid数组]}}
     * @param {*} context
     * @returns {peripheral}
     */
    static addDiscoverCharacteristicsObserver(aPeripheral, onevent, context) {
        return peripheral.addObserver(aPeripheral, "discoverCharacteristics", onevent, context);
    }

    /**
     * 移除观察者：发现特征
     * @param {Object} aPeripheral
     * @param {function} onevent
     * @returns {peripheral}
     */
    static removeDiscoverCharacteristicsObserver(aPeripheral, onevent) {
        return peripheral.removeObserver(aPeripheral, "discoverCharacteristics", onevent);
    }

    /**
     * 添加观察者：写值回应，如果返回的事件存在error成员说明失败
     * @param {Object} aPeripheral
     * @param {function} onevent
     * **事件结构**
     * - uuid string
     * - error object
     *      - errMsg string 失败描述
     *      - errCode Number 错误码
     *           + 1 写值失败
     * @param {*} context
     * @returns {peripheral}
     */
    static addWriteValueObserver(aPeripheral, onevent, context) {
        return peripheral.addObserver(aPeripheral, "writeValue", onevent, context);
    }

    /**
      * 移除观察者：写值回复
      * @param {Object} aPeripheral
      * @param {function} onevent
      * @returns {peripheral}
      */
    static removeWriteValueObserver(aPeripheral, onevent) {
        return peripheral.removeObserver(aPeripheral, "writeValue", onevent);
    }

    /**
     * 添加观察者：收到通知
     * @param {Object} aPeripheral
     * @param {function} onevent 事件结构 {data:{value:"具体通讯数据"}}
     * @param {*} context
     * @returns {peripheral}
     */
    static addUpdateValueObserver(aPeripheral, onevent, context) {
        return peripheral.addObserver(aPeripheral, "updateValue", onevent, context);
    }

    /**
     * 移除观察者：收到数据
     * @param {Object} aPeripheral
     * @param {function} onevent
     * @returns {peripheral}
     */
    static removeUpdateValueObserver(aPeripheral, onevent) {
        return peripheral.removeObserver(aPeripheral, "updateValue", onevent);
    }

    /**
     * 扫描外设的服务，一般在连接成功后执行扫描
     * @param {Object} aPeripheral 外设，结构{uudid:设备uuid}
     * @param {Array<string>} services 指定想要扫描的任意个服务的uuid，结构["service1的uuid","service2的uuid"...]
     */
    static discoverServices(aPeripheral, services) {
        if (!Native.getInstance().isHos()) {
            return;
        }
        Native.getInstance().callNative("ble",
            {
                "toPeripheral": true,
                "method": "discoverServices",
                "peripheral": aPeripheral.uuid,
                "services": services ? services : []
            });
    }

    /**
     * 扫描服务下的特征，一般在扫描到服务器后执行扫描特征
     * @param {Object} aPeripheral 外设，结构{uudid:设备uuid}
     * @param {string} service 指定特征所在的服务
     * @param {Array<string>} characteristics 指定要发现的特征uuid
     */
    static discoverCharacteristics(aPeripheral, service, characteristics) {
        if (!Native.getInstance().isHos()) {
            return;
        }
        Native.getInstance().callNative("ble",
            {
                "toPeripheral": true,
                "method": "discoverCharacteristics",
                "peripheral": aPeripheral.uuid,
                "service": service,
                "characteristics": characteristics
            }, false);
    }

    /**
     * 设置通知值，一般在扫描到读特征值后，设置为true。可以理解为使能通知（读）通道。接收通知数据
     * 需通过addUpdateValueObserver添加监听
     * @param {Object} aPeripheral 外设，结构{uudid:设备uuid}
     * @param {string} service 指定要写数据的特征所属的服务uuid
     * @param {string} characteristic 指定处理通知的特征uuid
     * @param {Boolean} value true:接收通知，false:一般用不到
     * @param {string} encoding （未实现）会影响addUpdateValueObserver监听函数所收到数据格式，
     *                          可选 "default"|"string"|"base64"
     */
    static setNotifyValue(aPeripheral, service, characteristic, value, encoding) {
        if (!Native.getInstance().isHos()) {
            return;
        }
        Native.getInstance().callNative("ble",
            {
                "toPeripheral": true,
                "method": "setNotifyValue",
                "peripheral": aPeripheral.uuid,
                "service": service, // Unuse
                "characteristic": characteristic,
                "value": value,
                "readType": encoding ? encoding : "default"
            }, false);
    }

    /**
     * 写二进制数据，如果发送字符串，可以直接使用writeStringValue
     * @param {Object} aPeripheral 外设，结构{uudid:设备uuid}
     * @param {string} service 指定要写数据的特征所属的服务uuid
     * @param {string} characteristic 指定要写数据的特征uuid
     * @param {string} value 需为hex string，系统会转换为对应的二进制发送, 不分大小写，可以空格和换行, e.g "001122AABBFF"
     * @param {Boolean} withoutResponse true:withoutResponse false:withResponse
     */
    static writeValue(aPeripheral, service, characteristic, value, withoutResponse) {
        if (!Native.getInstance().isHos()) {
            return;
        }
        Native.getInstance().callNative("ble",
            {
                "toPeripheral": true,
                "method": "writeValue",
                "peripheral": aPeripheral.uuid,
                "characterictic": characteristic,
                "service": service,
                "dataValue": value,
                "type": withoutResponse?"withoutResponse":"withResponse"
            },false);
    }

    /**
     * 以utf8编码写入外设
     * @param {Object} aPeripheral 外设，结构{uudid:设备uuid}
     * @param {string} service 指定要写数据的特征所属的服务
     * @param {string} characteristic 指定要写数据的特征uuid
     * @param {string} value 发送的字符串
     * @param {Boolean} withoutResponse true:withoutResponse false:withResponse
     */
    static writeStringValue(aPeripheral, service, characteristic, value, withoutResponse) {
        if (!Native.getInstance().isHos()) {
            return;
        }
        Native.getInstance().callNative("ble",
            {
                "toPeripheral": true,
                "method": "writeValue",
                "peripheral": aPeripheral.uuid,
                "characterictic": characteristic,
                "service": service, // Unuse
                "stringValue": value,
                "type": withoutResponse?"withoutResponse":"withResponse"
            }, false);
    }

    /**
     * 读数据，通过addUpdateValueObserver返回
     * @param aPeripheral 外设
     * @param service 指定特征所在服务
     * @param characteristic 指定读特征
     */
    static readValue(aPeripheral, service, characteristic) {
        if (!Native.getInstance().isHos()) {
            return;
        }
        Native.getInstance().callNative("ble",
            {
                "toPeripheral": true,
                "method": "readValue",
                "peripheral": aPeripheral.uuid,
                "characterictic": characteristic,
                "service": service // Unuse
            }, false);
    }


}

export {central, peripheral};
